﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using Tesla.Bounding;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;
using Tesla.Scene;
using Tesla.Scene.Shape;

namespace Tesla.Util {
    /// <summary>
    /// Debugger for visualizing the various parts of a scene graph or specific geometry. Contains utilities for drawing
    /// normals, tangents, binormals, and bounding volumes.
    /// </summary>
    public static class GeometryDebugger {
        //For bounding volume visualization
        private static Box _boundingBox;
        private static Sphere _boundingSphere;

        //For tangent basis visualization
        private static LineBatch _normalsBatch;
        private static LineBatch _tangentsBatch;
        private static LineBatch _binormalsBatch;

        /// <summary>
        /// Gets or sets the scaling ratio for rendering the tangent basis lines.
        /// </summary>
        public static float BasisScaleRatio {
            get {
                return _normalsBatch.ScaleRatio;
            }
            set {
                _normalsBatch.ScaleRatio = value;
                _tangentsBatch.ScaleRatio = value;
                _binormalsBatch.ScaleRatio = value;
            }
        }

        static GeometryDebugger() {
            Engine.EngineInitialized += new EventHandler(LoadContent);
            Engine.EngineReinitialized += new EventHandler(LoadContent);
            LoadContent(null, EventArgs.Empty);
        }

        private static void LoadContent(Object sender, EventArgs args) {
            Material colorMat = Engine.RenderSystemProvider.DefaultContent.Load<Material>("BasicColor.tem").Clone();
            Material vertColorMat = Engine.RenderSystemProvider.DefaultContent.Load<Material>("BasicVertColor.tem").Clone();

            //TODO: Either create another batch or find an instancing method for drawing bounding volumes,
            //performance will suffer the more objects we have...
            _boundingBox = new Box("BoundingBox", Vector3.Zero, 1, 1, 1);
            _boundingBox.MeshData.UseDynamicVertexBuffer = true;
            _boundingBox.MeshData.UseDynamicIndexBuffer = false;
            _boundingBox.SceneHints.RenderBucketType = RenderBucketType.Skip;
            _boundingBox.Material = colorMat;
            _boundingBox.SetRenderState(RasterizerState.CullNoneWireframe);

            _boundingSphere = new Sphere("BoundingSphere", 10, 10, 1);
            _boundingSphere.MeshData.UseDynamicVertexBuffer = true;
            _boundingSphere.MeshData.UseDynamicIndexBuffer = false;
            _boundingSphere.SceneHints.RenderBucketType = RenderBucketType.Skip;
            _boundingSphere.Material = colorMat;
            _boundingSphere.SetRenderState(RasterizerState.CullNoneWireframe);

            _normalsBatch = new LineBatch(vertColorMat, Color.Red, Color.Pink);
            _tangentsBatch = new LineBatch(vertColorMat, Color.Green, Color.YellowGreen);
            _binormalsBatch = new LineBatch(vertColorMat, Color.Blue, Color.BlueViolet);

            BasisScaleRatio = .5f;
        }

        /// <summary>
        /// Draws the vertex normals of the provided spatial. If the
        /// spatial is a Node, the child meshes of the spatial will have their
        /// vertex normals drawn, if the mesh is visible.
        /// </summary>
        /// <param name="scene">Scene to draw the vertex normals of.</param>
        /// <param name="renderer">The renderer</param>
        public static void DrawNormals(Spatial scene, IRenderer renderer) {
            DrawNormals(scene, renderer, true);
        }

        /// <summary>
        /// Draws the vertex normals of the provided spatial. If the spatial
        /// is a Node and recurse is true, the child meshes of the spatial
        /// will have their vertex normals drawn, if the mesh is visible.
        /// </summary>
        /// <param name="scene">Scene to draw the vertex normals of.</param>
        /// <param name="renderer">The renderer</param>
        /// <param name="recurse">True if the children of the scene should have their vertex normals drawn.</param>
        public static void DrawNormals(Spatial scene, IRenderer renderer, bool recurse) {
            BoundingVolume volume = scene.WorldBounding;
            bool visible = true;
            if(volume != null) {
                //Check if its visible
                visible = renderer.CurrentCamera.Contains(volume) != ContainmentType.Outside;
                //If we're a mesh, render
                Mesh mesh = scene as Mesh;
                if(visible && mesh != null) {
                    _normalsBatch.Draw(mesh, renderer, 0);
                }
            }

            //If scene is actually a node, check children if recurse is enabled and the node is visible.
            if(scene is Node && (recurse && visible)) {
                Node node = scene as Node;
                foreach(Spatial child in node) {
                    DrawNormals(child, renderer, true);
                }
            }
        }

        /// <summary>
        /// Draws the tangent basis (Normal, Tangent, Binormal) of the provided spatial. If the
        /// spatial is a Node, the child meshes of the spatial will have their
        /// tangent basis drawn, if the mesh is visible.
        /// </summary>
        /// <param name="scene">Scene to draw the tangent basis of.</param>
        /// <param name="renderer">The renderer</param>
        public static void DrawTangentBasis(Spatial scene, IRenderer renderer) {
            DrawTangentBasis(scene, renderer, true);
        }

        /// <summary>
        /// Draws the tangent basis (Normal, Tangent, Binormal) of the provided spatial. If the spatial
        /// is a Node and recurse is true, the child meshes of the spatial
        /// will have their tangent basis drawn, if the mesh is visible.
        /// </summary>
        /// <param name="scene">Scene to draw the tangent basis of.</param>
        /// <param name="renderer">The renderer</param>
        /// <param name="recurse">True if the children of the scene should have their tangent basis drawn.</param>
        public static void DrawTangentBasis(Spatial scene, IRenderer renderer, bool recurse) {
            BoundingVolume volume = scene.WorldBounding;
            bool visible = true;
            if(volume != null) {
                //Check if its visible
                visible = renderer.CurrentCamera.Contains(volume) != ContainmentType.Outside;
                //If we're a mesh, render
                Mesh mesh = scene as Mesh;
                if(visible && mesh != null) {
                    _normalsBatch.Draw(mesh, renderer, 0);
                    _tangentsBatch.Draw(mesh, renderer, 1);
                    _binormalsBatch.Draw(mesh, renderer, 2);
                }
            }

            //If scene is actually a node, check children if recurse is enabled and the node is visible.
            if(scene is Node && (recurse && visible)) {
                Node node = scene as Node;
                foreach(Spatial child in node) {
                    DrawTangentBasis(child, renderer, true);
                }
            }
        }

        /// <summary>
        /// Draws the spatial's bounding volumes, if visible to
        /// the renderer's currently active camera. If the spatial is
        /// a Node, the children of the spatial will be rendered if visible.
        /// </summary>
        /// <param name="scene">Scene to draw the bounds of.</param>
        /// <param name="renderer">The renderer</param>
        public static void DrawBounds(Spatial scene, IRenderer renderer) {
            DrawBounds(scene, renderer, true);
        }

        /// <summary>
        /// Draws the spatial's bounding volumes, if visible to the renderer's
        /// currently active camera. If the spatial is a Node and recurse is
        /// requested, the children of the spatial will be rendered if visible.
        /// </summary>
        /// <param name="scene">Scene to draw the bounds of.</param>
        /// <param name="renderer">The renderer</param>
        /// <param name="recurse">True if the children of the scene, if any, should have their volumes drawn.</param>
        public static void DrawBounds(Spatial scene, IRenderer renderer, bool recurse) {
            BoundingVolume volume = scene.WorldBounding;
            bool visible = true;
            //Render the volume if it exists
            if(volume != null) {
                //Check if its visible, if not don't render
                visible = renderer.CurrentCamera.Contains(volume) != ContainmentType.Outside;
                if(visible) {
                    DrawBoundingVolume(volume, renderer);
                }
            }

            //Render child bounds if the spatial is a node and recurse is requested.
            //If the node's volume wasn't rendered because it wasn't visible,
            //then child volumes are guaranteed to not be visible either, so don't bother looping.
            if(scene is Node && (recurse && visible)) {
                Node node = scene as Node;
                foreach(Spatial child in node) {
                    DrawBounds(child, renderer, true);
                }
            }
        }

        /// <summary>
        /// Draws the bounding volume.
        /// </summary>
        /// <param name="volume">Volume to draw</param>
        /// <param name="renderer">Renderer to use</param>
        public static void DrawBoundingVolume(BoundingVolume volume, IRenderer renderer) {
            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    DrawBoundingBox(volume as BoundingBox, renderer);
                    return;
                case BoundingType.OBB:
                    return;
                case BoundingType.Sphere:
                    DrawBoundingSphere(volume as BoundingSphere, renderer);
                    return;
            }
        }

        private static void DrawBoundingBox(BoundingBox bb, IRenderer r) {
            _boundingBox.SetData(bb.Center, bb.Extents);
            _boundingBox.Draw(r);
        }

        private static void DrawBoundingSphere(BoundingSphere bs, IRenderer r) {
            _boundingSphere.SetData(bs.Center, 10, 10, bs.Radius);
            _boundingSphere.Draw(r);
        }
    }
}